﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace PENet
{
    [Serializable]
    public abstract class KCPMsg { }

    public class KCPNet<TSession, TMsg> 
        where TSession : KCPSession<TMsg> , new()
        where TMsg : KCPMsg, new()
    {
        UdpClient udp;
        IPEndPoint remotePoint;

        private CancellationTokenSource cts;
        private CancellationToken ct;

        public KCPNet()
        {
            cts = new CancellationTokenSource();
            ct = cts.Token;
        }
        #region Server
        
        private Dictionary<uint, TSession> sessionDic;
        public void StartAsServer(string ip,int port)
        {
            sessionDic = new Dictionary<uint, TSession>();

            udp = new UdpClient(new IPEndPoint(IPAddress.Parse(ip), port));
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                udp.Client.IOControl((IOControlCode)(-1744830452), new byte[] { 0, 0, 0, 0 }, null);
            }
            remotePoint = new IPEndPoint(IPAddress.Parse(ip), port);
            KCPTool.ColorLog(KCPLogColor.Green, "Server Start...");
            Task.Run(ServerRecive, ct);
        }

        async void ServerRecive()
        {
            UdpReceiveResult result;
            while(true)
            {
                try
                {
                    if(ct.IsCancellationRequested)
                    {
                        KCPTool.ColorLog(KCPLogColor.Cyan, "SeverRecive Task is Cancelled.");
                        break;
                    }
                    result = await udp.ReceiveAsync();
                    uint sid = BitConverter.ToUInt32(result.Buffer, 0);
                    if(sid == 0)
                    {
                        sid = GenerateUniqueSessionID();
                        byte[] sid_bytes = BitConverter.GetBytes(sid);
                        byte[] conv_bytes = new byte[8];
                        Array.Copy(sid_bytes,0, conv_bytes, 4, 4);
                        //为什么不重发udp：因为如果client没收到，会重新申请，服务器就不用确保一定到达
                        SendUDPMsg(conv_bytes, result.RemoteEndPoint);
                    }
                    else
                    {
                        TSession session;
                        if(!sessionDic.TryGetValue(sid,out session))
                        {
                            //为什么这里才new加入字典：因为可能会重发sid，如果之前就加入就会new多余session
                            session = new TSession();
                            session.InitSession(sid, SendUDPMsg, result.RemoteEndPoint);
                            session.OnSessionClose = OnServerSessionClose;
                            lock(sessionDic)
                            {
                                sessionDic.Add(sid, session);
                            }
                        }
                        session.ReciveData(result.Buffer);
                    }
                }
                catch(Exception e)
                {
                    KCPTool.Warn($"Server Udp Recive Data Exception:{e}");
                }
            }
        }

        private uint handOutSid = 0;
        public uint GenerateUniqueSessionID()
        {
            lock (sessionDic)
            {
                while (true)
                {
                    ++handOutSid;
                    if (handOutSid == uint.MaxValue)
                    {
                        handOutSid = 1;
                    }
                    if (!sessionDic.ContainsKey(handOutSid))
                    {
                        break;
                    }

                }
            }
            return handOutSid;
        }

        void OnServerSessionClose(uint sid)
        {
            if (sessionDic.ContainsKey(sid))
            {
                lock (sessionDic)
                {
                    sessionDic.Remove(sid);
                    KCPTool.Warn($"Session:{sid} Remove.");
                }
            }
            else
            {
                KCPTool.Error($"Session:{sid} Cannot Find In SessionDic");
            }
        }

        public void CloseServer()
        {
            foreach (var item in sessionDic)
            {
                item.Value.CloseSession();
            }
            sessionDic = null;

            if (udp != null)
            {
                udp.Close();
                udp = null;
                cts.Cancel();
            }
        }

        public void BroadCastMsg(TMsg msg)
        {
            byte[] bytes = KCPTool.KCPSerialize<TMsg>(msg);
            foreach (var item in sessionDic)
            {
                item.Value.SendMsg(bytes);
            }
        }
        #endregion
        #region Client
        public TSession clientSession;
        public void StartAsClient(string ip, int port)
        {
            udp = new UdpClient(0);
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                udp.Client.IOControl((IOControlCode)(-1744830452), new byte[] { 0, 0, 0, 0 }, null);
            }
            remotePoint = new IPEndPoint(IPAddress.Parse(ip), port);
            KCPTool.ColorLog(KCPLogColor.Green, "Client Start...");

            Task.Run(ClientRecive, ct);
        }

        async void ClientRecive()
        {
            UdpReceiveResult result;
            while (true)
            {
                try
                {
                    if (ct.IsCancellationRequested)
                    {
                        KCPTool.ColorLog(KCPLogColor.Cyan, "ClientRecive Task is Cancelled.");
                        break;
                    }

                    result = await udp.ReceiveAsync();
                    if (Equals(remotePoint, result.RemoteEndPoint))
                    {
                        uint sid = BitConverter.ToUInt32(result.Buffer, 0);
                        if (sid == 0)
                        {
                            //初始化
                            if (clientSession != null && clientSession.IsConnected)
                            {
                                //已经建立连接，初始化完成了，收到了多的sid,直接丢弃。
                                KCPTool.Warn("Client is Init Done,Sid Surplus.");
                            }
                            else
                            {
                                //未初始化，收到服务器分配的sid数据，初始化一个客户端session
                                sid = BitConverter.ToUInt32(result.Buffer, 4);
                                KCPTool.ColorLog(KCPLogColor.Green, $"UDP Init Sid:{sid}");

                                // 新建会话
                                clientSession = new TSession();
                                clientSession.InitSession(sid, SendUDPMsg, remotePoint);
                                clientSession.OnSessionClose = OnClientSessionClose;
                            }
                        }
                        else
                        {
                            //处理业务逻辑
                            if (clientSession != null && clientSession.IsConnected)
                            {
                                clientSession.ReciveData(result.Buffer);
                            }
                            else
                            {
                                //没初始化且sid!=0，数据消息提前到了，直接丢弃消息，直到初
                                //始化完成，kcp重传再开始处理。
                                KCPTool.Warn("Client is on Initing...So Miss this Data");
                            }
                        }
                    }
                    else
                    {
                        KCPTool.Warn("Client Recive's EndPoint Wrong>>" +
                            $"Address:{remotePoint.Address} Port:{remotePoint.Port}");
                    }
                }
                catch (Exception e)
                {
                    KCPTool.Warn($"Client Udp Recive Data Exception:{e.ToString()}");
                }
            }
        }

        public void ConnectServer(int interval = 200, int maxInterval = 5000,int maxTimes = 4)
        {
            Task.Run(async () =>
            {
                int connectCounts = 0;
                while(true)
                {
                    bool success = await TryConnectServer(interval, maxInterval);
                    if(success)
                    {
                        KCPTool.ColorLog(KCPLogColor.Green, "ConnectServer Success.");
                        break;
                    }
                    if(++connectCounts >= 4)
                    {
                        KCPTool.Warn($"Connect Failed {connectCounts} Times,Check Your Network.");
                        break;
                    }
                    KCPTool.Warn($"Connect Faild {connectCounts} Times.Retry...");
                    await Task.Delay(3000);
                }
            });
        }

        public async Task<bool> AsyncConnectServer(int interval = 200, int maxInterval = 5000, int maxTimes = 4)
        {
            int connectCounts = 0;
            while (true)
            {
                bool success = await TryConnectServer(interval, maxInterval);
                if (success)
                {
                    KCPTool.ColorLog(KCPLogColor.Green, "ConnectServer Success.");
                    return true;
                }
                if (++connectCounts >= 4)
                {
                    KCPTool.Warn($"Connect Failed {connectCounts} Times,Check Your Network.");
                    return false;
                }
                KCPTool.Warn($"Connect Faild {connectCounts} Times.Retry...");
                await Task.Delay(3000);
            }
        }

        public async Task<bool> TryConnectServer(int interval, int maxInterval)
        {
            SendUDPMsg(new byte[4], remotePoint);
            int checkTimes = 0;
            while (true)
            {
                await Task.Delay(interval);
                checkTimes += interval;
                if (clientSession != null && clientSession.IsConnected)
                    return true;
                if (checkTimes > maxInterval)
                    return false;
            }
        }

        

        void OnClientSessionClose(uint sid)
        {
            cts.Cancel();
            udp?.Close();
            udp = null;
            KCPTool.Warn($"Client Session Close,sid:{sid}");
        }

        public void CloseClient()
        {
            if (clientSession != null)
            {
                clientSession.CloseSession();
            }
        }

        public void ClientSendMsg(TMsg msg) => clientSession.SendMsg(msg);
        public void ClientSendMsg(byte[] msg) => clientSession.SendMsg(msg);
        public bool IsConnectServer => clientSession != null && clientSession.IsConnected;
        #endregion

        void SendUDPMsg(byte[] bytes, IPEndPoint remotePoint)  
            => udp?.SendAsync(bytes, bytes.Length, remotePoint);
    }
}
